/*
* Copyright (c) 2008 Thierry Coppey, EPFL
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Thierry Coppey nor EPFL, nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THIERRY COPPEY AND EPFL ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THIERRY COPPEY OR EPFL BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#import "Sniffer.h"
#import "pthread.h"
#import <ifaddrs.h>
#import <sys/stat.h>

#define BUFSIZE 8192

void* bpfr_thr(void* arg) {
	NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
	char buf[BUFSIZE],*b; int l,n,f,p[2]; pipe(p); Sniffer* sniffer=arg; SnifferPacket *pkt;
	switch (f=fork()) {
		case -1: goto cleanup;
		case  0: dup2(p[1],1); close(p[1]); close(p[0]); close(2); open("/dev/null",O_WRONLY);
			
			// Debug (TCK)
			//	execlp("/Server/src/osx/MacShark/bpfr","bpfr",[[sniffer selectedIf] UTF8String],0);
				execlp([[[NSBundle mainBundle] pathForAuxiliaryExecutable:@"bpfr"] UTF8String],
					"bpfr",[[sniffer selectedIf] UTF8String],0);
			_exit(1);
	}
	close(p[1]); 
	[sniffer setPid:f];

	memset(buf,0,BUFSIZE);
	while (n=read(p[0],buf,BUFSIZE)) {
		for(b=buf;1;b+=l) {
			l=BPF_WORDALIGN(((struct bpf_hdr*)b)->bh_hdrlen+((struct bpf_hdr*)b)->bh_caplen);
			if (l<=0||l>=n||b+l>n+buf) break;
			if (pkt=[SnifferPacket packetWith:b]) { [sniffer addPacket:pkt]; [pkt release]; }
		}
		memset(buf,0,n);
	}

	close(p[0]); waitpid(f,0,0); [sniffer setPid:0];
	cleanup: [pool release]; pthread_exit(0); return 0;
}

@implementation Sniffer
// ---------------------------------------------------------------------------------------------
// Initializations

- (id)init {
	if (self=[super init]) {
		difs=[[NSMutableArray alloc] init]; data=[[NSMutableArray alloc] init]; ddlg=nil; dpkt=nil;
		dnfo=[[SnifferTreeNode alloc] init]; dhex=[[NSMutableArray alloc] init]; tpid=0;
	}
	return self;
}

- (void)dealloc {
	if (tpid) kill(tpid,SIGKILL);
	[difs release]; [data release]; [dnfo release]; [dhex release];
	[SnifferPacket cleanup]; [super dealloc];
}

- (void)awakeFromNib {
	[content setFont:[NSFont fontWithName:@"Courier" size:11.0]];
	[netif removeAllItems];
	struct ifaddrs* ifa, *p;
	if (!getifaddrs(&ifa)) {
		for (p=ifa;p;p=p->ifa_next) if (p->ifa_addr->sa_family==AF_INET) {
			[netif addItemWithTitle:[NSString stringWithFormat:@"%s (%s)",p->ifa_name,inet_ntoa(((struct sockaddr_in*)p->ifa_addr)->sin_addr)]];
			[difs addObject:[NSString stringWithFormat:@"%s",p->ifa_name]];
		}
		freeifaddrs(ifa);	
	}
	if ([netif numberOfItems]) [netif selectItemAtIndex:0];
}

- (void) addPacket:(SnifferPacket*) packet {
	NSString *trsp=[packet transport],*src=[packet source],*dst=[packet destination];
	NSMutableArray *pdlg=nil,*ppkt=nil; int i,n=[data count],r=0;
	for (i=0;i<n;i++) if ([trsp isEqualToString:[[data objectAtIndex:i] objectForKey:@"trsp"]]) {
		pdlg=[[data objectAtIndex:i] objectForKey:@"data"]; break;
	}
	if (!pdlg) {
		NSMutableDictionary* p=[[NSMutableDictionary alloc] init];
		[p setObject:trsp forKey:@"trsp"];
		[p setObject:(pdlg=[[NSMutableArray alloc] init]) forKey:@"data"];
		[data addObject:p];
	}
	n=[pdlg count];
	for (i=0;i<n;i++) {
		NSString *dsrc=[[pdlg objectAtIndex:i] objectForKey:@"src"],*ddst=[[pdlg objectAtIndex:i] objectForKey:@"dst"];
		//if ([src isEqualToString:dsrc]&&[dst isEqualToString:ddst]||[src isEqualToString:ddst]&&[dst isEqualToString:dsrc]&&(r=1)) {
		// fixed : different sourceports for same service (typically HTTP)
		if (
			([src isEqualToString:dsrc]||[dst isEqualToString:ddst])||
			([src isEqualToString:ddst]&&(r=1)||[dst isEqualToString:dsrc]&&(r=1))
		) {
		
			int p=[[[pdlg objectAtIndex:i] objectForKey:@"pkts"] intValue]+1;
			[[pdlg objectAtIndex:i] setObject:[NSNumber numberWithInt:p] forKey:@"pkts"];
			ppkt=[[pdlg objectAtIndex:i] objectForKey:@"data"]; break;
		}
	}
	if (!ppkt) {
		NSMutableDictionary* p=[[NSMutableDictionary alloc] init];
		[p setObject:src forKey:@"src"]; [p setObject:dst forKey:@"dst"];
		[p setObject:[packet protocol] forKey:@"proto"];
		[p setObject:[NSNumber numberWithInt:1] forKey:@"pkts"];
		[p setObject:(ppkt=[[NSMutableArray alloc] init]) forKey:@"data"];
		[pdlg addObject:p];
	}
	NSMutableDictionary* p=[[NSMutableDictionary alloc] init];
	[p setObject:[packet time:false] forKey:@"time"];
	[p setObject:r?@"<-":@"->" forKey:@"dir"];
	[p setObject:packet forKey:@"data"];
	[ppkt addObject:p];
	[networks reloadData]; [dialogs reloadData]; [packets reloadData];
}

// ---------------------------------------------------------------------------------------------
// Thread-related functions

- (void) setPid:(int)pid {
	tpid=pid;
	[capture setLabel:(pid?@"Arrêter":@"Capturer")];
	NSString* file=[[NSBundle mainBundle] pathForResource:(pid?@"SnifferStop":@"SnifferPlay") ofType:@"png"];
	[capture setImage:[[NSImage alloc] initWithContentsOfFile:file]];
}
- (NSString*) selectedIf { return [difs objectAtIndex:[netif indexOfSelectedItem]]; }

- (BOOL) setPriv {
	AuthorizationRef authRef; AuthorizationRights rights; AuthorizationFlags flags; AuthorizationItem item; OSStatus err;
	char *path=(char*)[[[NSBundle mainBundle] pathForAuxiliaryExecutable:@"bpfr"] UTF8String];
	char *args[]={"--setuid",NULL}; struct stat s; stat(path,&s); if (!s.st_uid&&(s.st_mode&0004500)) return YES;
	if (AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, flags, &authRef)!=errAuthorizationSuccess) return NO;
	item.name=kAuthorizationRightExecute; item.valueLength=strlen(path); item.value=path; item.flags=0;
	rights.count=1; rights.items=&item; flags=kAuthorizationFlagInteractionAllowed|kAuthorizationFlagExtendRights;
	err=AuthorizationCopyRights(authRef, &rights, kAuthorizationEmptyEnvironment, flags, NULL);
	if (err!=errAuthorizationSuccess) { AuthorizationFree(authRef, kAuthorizationFlagDestroyRights); return NO; }
	err=AuthorizationExecuteWithPrivileges(authRef, path, 0, args, NULL);
	AuthorizationFree(authRef,kAuthorizationFlagDestroyRights); return err==errAuthorizationSuccess;
}

- (IBAction) capture:(id) Sender {
	if (tpid) kill(tpid,SIGTERM);
	else if ([self setPriv]) pthread_create(&tthr,nil,bpfr_thr,self);
}

- (IBAction) clear:(id) Sender {
	if (tpid) kill(tpid,SIGTERM);
	ddlg=nil; dpkt=nil; [data removeAllObjects]; [dnfo removeAllChild]; [content setString:@""];
	[networks reloadData]; [dialogs reloadData]; [packets reloadData]; [properties reloadData];
}

// ---------------------------------------------------------------------------------------------
// Data displaying (hex+text)

- (NSAttributedString *) hexString:(NSString*)str {
	NSFont *font = [NSFont fontWithName:@"Courier" size:11.0];
	NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
	NSAttributedString *attrString =[[NSAttributedString alloc] initWithString:str attributes:attrsDictionary];
	return attrString;
}

- (void) viewData:(NSData*) packetData {
	[dhex removeAllObjects];
	int i,w=16,n=[packetData length]; u_char* b=(u_char*)[packetData bytes]; NSMutableDictionary* l=nil;
	NSMutableString *hr=[NSMutableString stringWithString:@""],*ha=[NSMutableString stringWithString:@""];
	for (i=0;i<n;i++) {
		if (!(i%w)) {
			l=[[NSMutableDictionary alloc] init];
			[l setObject:[NSString stringWithFormat:@"0x%04X",i] forKey:@"off"];
			//[l setObject:[self hexString:[NSString stringWithFormat:@"0x%04X",i]] forKey:@"off"];
		} else if (!(i%2)) [hr appendString:@" "];
		[hr appendFormat:@"%02X",b[i]];
		[ha appendFormat:@"%c",b[i]>=32&&b[i]<=126?b[i]:'.'];
		if ((i%w)==w/2-1) [ha appendString:@" "];
		if ((i%w)==w-1) {
			[l setObject:[NSString stringWithString:hr] forKey:@"hex"];
			[l setObject:[NSString stringWithString:ha] forKey:@"ascii"];

			//[l setObject:[self hexString:hr] forKey:@"hex"];
			//[l setObject:[self hexString:ha] forKey:@"ascii"];
			[dhex addObject:l];
			[hr setString:@""]; [ha setString:@""];
		}
	}
	if (l&&(i%w)!=w-1) {
		//[l setObject:[self hexString:hr] forKey:@"hex"];
		//[l setObject:[self hexString:ha] forKey:@"ascii"];
		[dhex addObject:l];
	}
	[hexview reloadData];
		
	NSMutableString *r=[NSMutableString stringWithString:@""];
	for (i=0;i<n;i++) [r appendFormat:@"%c",b[i]]; [content setString:r];
}

// ---------------------------------------------------------------------------------------------
// Transport/Dialog/Packet (and hex) TableViews

- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
	if (tableView==networks) return [data count];
	if (tableView==dialogs&&ddlg) return [ddlg count];
	if (tableView==packets&&dpkt) return [dpkt count];
	if (tableView==hexview) return [dhex count];
	return 0;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)col row:(NSInteger)row {
	if (tableView==networks) return [[data objectAtIndex:row]objectForKey:[col identifier]];
	if (tableView==dialogs) return [[ddlg objectAtIndex:row]objectForKey:[col identifier]];
	if (tableView==packets) return [[dpkt objectAtIndex:row]objectForKey:[col identifier]];
	if (tableView==hexview) return [[dhex objectAtIndex:row]objectForKey:[col identifier]];
	return nil;
}

- (NSIndexSet *)tableView:(NSTableView *)tableView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes {
	int i=[proposedSelectionIndexes count]?[proposedSelectionIndexes firstIndex]:-1;
	if (tableView==networks) {
		ddlg=i<0?nil:[[data objectAtIndex:i] objectForKey:@"data"]; [self tableView:dialogs sortDescriptorsDidChange:nil];
		[dialogs reloadData]; [self tableView:dialogs selectionIndexesForProposedSelection:[dialogs selectedRowIndexes]];
	}
	if (tableView==dialogs) {
		dpkt=i<0?nil:[[ddlg objectAtIndex:i] objectForKey:@"data"]; [self tableView:packets sortDescriptorsDidChange:nil];
		[packets reloadData]; [self tableView:packets selectionIndexesForProposedSelection:[packets selectedRowIndexes]];
	}
	if (tableView==packets) {
		NSData* d=[[[dpkt objectAtIndex:i] objectForKey:@"data"] content:dnfo];
		[properties reloadData]; [properties expandItem:nil expandChildren:true];
		[self viewData:d]; [hexview reloadData];
	}
	return proposedSelectionIndexes;
}

- (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors {
	if (![[tableView sortDescriptors] count]) return;
	NSArray* d=[NSArray arrayWithObject:[[tableView sortDescriptors] objectAtIndex:0]];
	if (tableView==networks) [data sortUsingDescriptors:d];
	if (tableView==dialogs) [ddlg sortUsingDescriptors:d];
	if (tableView==packets) [dpkt sortUsingDescriptors:d];
	[tableView reloadData]; [self tableView:tableView selectionIndexesForProposedSelection:[tableView selectedRowIndexes]];
}

// ---------------------------------------------------------------------------------------------
// Property OutlineView

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
	if (item) return [item childAtIndex:index]; else return [dnfo childAtIndex:index];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { return [item childCount]>0; }
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { return item?[item childCount]:[dnfo childCount]; }
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)col byItem:(id)item {
	if ([[col identifier] isEqualToString:@"name"]) return [item name]; else return [item value];
}

@end
